﻿using System;
using System.Reflection;
using System.Collections.Generic;

namespace SCG.General
{
    //Dynamically loads Types from Assemblies, and facilitates dynamic creation of instances
    public class TypeLoader : IComparer<Type>
    {
        //constructor
        public TypeLoader(string baseClassName, string interfaceName)
        {
            _baseClassName = baseClassName;
            _interfaceName = interfaceName;

            //Load from caller and executor
            Assembly asmCaller = Assembly.GetCallingAssembly();
            Assembly asmExecuting = Assembly.GetExecutingAssembly();
            LoadTypes(asmCaller);
            LoadTypes(asmExecuting);
        }

        //Allow loading types from different Assembly after construction
        public void LoadTypes(Assembly asm)
        {
            //Load from each Assembly only once
            if (_assemblies.Contains(asm))
                return;

            //Check that parameters are assigned
            bool paramSet = true;
            if (_baseClassName != null && _baseClassName != "")
                paramSet = true;
            if (_interfaceName != null && _interfaceName != "")
                paramSet = true;
            if (!paramSet)
                throw new ArgumentException("Must assign a Base Class or an Interface Name for TypeLoader");

            //Remember that we loaded from this Assembly
            _assemblies.Add(asm);

            //Load the Types
            Type[] types = asm.GetTypes();          
            foreach (Type t in types)
            {
                //Interface specified, does it implement?
                if (_interfaceName != null && _interfaceName != "")               
                    if (t.GetInterface(_interfaceName, true) == null)
                        continue;

                //Base class specified, does it descend from it?
                if (_baseClassName != null && _baseClassName != "")
                    if ((t.IsAbstract && _baseClassName != "Object") || !DescendsFromBase(t))
                        continue;

                //Add the reference
                _types.Add(t);
            }

            //Sort the list
            _types.Sort(this);
        }

        //Access the types that were discovered     
        public List<Type> Types
        {
            get
            {
                return _types;
            }
        }

        //access the assemblies
        public List<Assembly> Assemblies
        {
            get
            {
                return _assemblies;
            }
        }

        //Create an instance from a type
        public Object CreateInstance(Type t)
        {
            return Activator.CreateInstance(t);
        }

        //Create an instance from a type name
        public Object CreateInstance(string typeName)
        {
            foreach (Type t in Types)
            {
                if (t.Name == typeName)
                    return Activator.CreateInstance(t);
            }
            return null;
        }

        //Comparer method used to alpha sort Types by name
        public int Compare(Type x, Type y)
        {
            return x.Name.CompareTo(y.Name);
        }

        //private members
        private List<Assembly> _assemblies = new List<Assembly>();
        private List<Type> _types = new List<Type>();
        private string _baseClassName = "";
        private string _interfaceName = "";

        //Determine if the Type descends from the BaseType
        private bool DescendsFromBase(Type t)
        {
            if (_baseClassName == "Object")
                return true;
            else if (t.Name == _baseClassName)
                return true;
            else if (t.BaseType == null)
                return false;
            else
                return DescendsFromBase(t.BaseType);
        }
    }
}
